iT邦幫忙

2026 iThome 鐵人賽

DAY 2
0
Software Development

同一套 Laravel 系統,測試怎麼寫才不會說謊系列 第 2

Day 02:一次 PHPUnit → Pest 的遷移,殘留了什麼

  • 分享至 

  • xImage
  •  

前言

「這個專案的測試是用 Pest 寫的還是 PHPUnit 寫的?」

如果你隨手打開這個系統的 tests/ 目錄亂逛,答案看起來很明確:滿眼都是 test()it()describe() 這種 Pest 語法,偶爾夾雜一支 class ExampleTest extends TestCase 的舊式寫法,像是誰忘了收拾的殘局。昨天我差點把這篇寫成「這個專案怎麼讓 Pest 跟 PHPUnit 兩套語法並存」,翻了一次 commit 歷史才發現自己想錯方向——這裡沒有「並存」這回事,只有一次還沒收尾乾淨的遷移

今天要講的不是「Pest 跟 PHPUnit 怎麼選」,而是一件更貼近真實維護現場的事:框架升級這種大範圍的改動,即使目標明確、也真的做完了,還是會留下一些沒被順手清掉的痕跡,而這些痕跡本身就是判斷這個專案測試成熟度的線索。

今日目標

  • 理解「兩套語法並存」跟「一次遷移還沒收尾」是兩種完全不同的專案狀態,寫作/審查時不能搞混
  • 看一次真實的升級 commit,理解一次框架升級可能同時夾帶哪些「順便做掉」的改動
  • 認識這個專案怎麼用 tests/Pest.php 統一設定測試基底
  • 學到一個判斷習慣:看到「兩種寫法並存」時,先去查 commit 歷史,不要直接假設是刻意設計

一個 commit,其實做了三件事

去查這個專案的 commit 歷史,會找到一個訊息寫著「升級到 Laravel 12,Pest v3,並把外部服務錄製重播機制換成自製測試替身」的 commit。光看這一行訊息,就能拆出三件表面上不相關、但其實同一次動手做掉的事:

  1. 框架大版本升級:Laravel 升級到最新的主版本,這種升級本身就有相容性風險,通常要花不少力氣驗證。
  2. 測試框架換成 Pest:從 PHPUnit 的 class-based 寫法換成 Pest 的函式式寫法。
  3. 外部服務測試替身換掉:原本用來錄製/重播外部 HTTP 呼叫的第三方套件,換成專案自己寫的測試替身類別(這部分會在後面 Day 16、Day 26 深入拆解)。

這件事想提醒的是:大範圍升級很少是單一動作,通常是「既然都要動這麼多測試檔案了,順便把另一件一直想做但沒空做的事也做掉」的組合拳。這也解釋了為什麼 commit 歷史裡還能找到更早一支專門「統一測試風格,全面採用 Pest 語法輔助函式」的 commit——遷移本身也不是一次到位,是分階段推進的。

殘留的那一支:為什麼還留著

回到那支還在用 class ExampleTest extends TestCase 寫法的檔案。它是 Laravel 專案初始化時框架自動產生的範例測試檔,內容通常只是驗證「應用程式能不能正常回應一個請求」這種最基本的健康檢查,本身沒有業務邏輯。

它為什麼沒被一起遷移成 Pest 語法?合理的推測是:這支測試從頭到尾沒人真的去動它,遷移的心力自然會優先花在有業務邏輯、會隨著功能開發持續變動的測試檔案上,一支從沒改過的範例測試,順位自然排到最後——甚至排到「還沒排到」。

這不是缺陷,而是真實專案裡「遷移優先序」很自然的樣子:先遷移會被頻繁修改、直接關係到功能正確性的測試,範例性質、幾乎不會再被碰的檔案,晚一點處理甚至不處理,都是合理的判斷,只要不影響測試套件整體能不能執行。

統一測試基底:Pest.php 做了什麼

Pest 有一個 PHPUnit 沒有的機制:tests/Pest.php 這支設定檔可以用 uses() 幫一整個資料夾底下的測試統一套用共用的 TestCase 跟 Trait,不用每支測試檔案自己 use 一次。

這個專案的 Pest.phpFeature 資料夾底下所有測試統一套用了專案自訂的 TestCase,並疊加了資料庫刷新機制,確保每個測試案例執行前資料庫都是乾淨狀態。這一行設定的意義是:把「每支測試都要記得刷新資料庫」這種容易被遺忘的樣板設定,從「開發者責任」降級成「框架設定,自動生效」。少了這一步,理論上還是可以在每支測試檔案裡各自手動宣告,但那樣的維護成本會隨著測試檔案數量增加而線性上升——這個系統目前有 63 支測試檔案,如果每一支都要自己記得設定一次,出錯的機率不會是零。

❌ 假設「兩套語法並存」是刻意設計

看到專案裡同時有 Pest 跟 PHPUnit 語法的測試檔案,
直接下結論:「這個團隊選擇讓兩套語法並存,
可能是想漸進式引入 Pest,不強迫全部改寫」。

→ 沒有查 commit 歷史,憑檔案現狀直接推測意圖,
  容易把「還沒做完的事」誤判成「刻意的設計決策」

✅ 先查歷史,再下結論

看到同樣的現狀,先跑一次 git log 對照這個檔案的歷史,
發現整個測試套件在某個時間點集中遷移到 Pest,
只有一支從未被修改過的範例測試沒被一起處理。

→ 正確結論:這是一次遷移的殘留,
  不是「刻意維持兩套語法」的設計選擇,
  這支殘留檔案的優先序低到目前還沒被排上議程

同一個現狀,兩種完全不同的判斷結果——差別只在有沒有去查一次歷史。 這個習慣不只適用於框架遷移,任何時候看到程式碼裡「看起來不一致」的地方,先假設它有一段沒被記錄下來的過程,比直接假設「這是刻意的」更接近真相。

今日思考題

回想你手上維護的專案,有沒有一次「大範圍升級」其實暗藏了其他順便做掉的改動?如果現在有新人看到那次升級的 diff,他們有辦法從 commit 訊息裡看出這件事,還是要靠口耳相傳才知道?

今日重點回顧

  • 「兩套語法並存」跟「一次遷移還沒收尾」是完全不同的判斷,看到不一致的地方要先查歷史再下結論
  • 一次框架升級的 commit,實際上同時做了三件事:升級 Laravel 大版本、遷移測試框架、換掉測試替身機制——大範圍改動常常是組合拳
  • 一支從未被修改過的範例測試檔案還留著舊語法,反映的是合理的遷移優先序,不是缺陷
  • tests/Pest.phpuses() 統一設定測試基底,把容易被遺忘的樣板設定從開發者責任降級成框架自動生效

明日預告

明天要講這個系統怎麼設計測試輔助函式——兩個看起來只是「省重複程式碼」的函式,背後其實藏著一個關於「測試環境要不要跟著遵守最小權限原則」的判斷。


上一篇
Day 01:系列介紹——同一套 Laravel 系統,測試怎麼寫才不會說謊
系列文
同一套 Laravel 系統,測試怎麼寫才不會說謊2
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言